<template>
  <el-row class="call-graph-analysis">
    <el-row class="input-block" :class="class_list.length===0?'input-before':'input-after'">
      <el-col :span="4" class="select">
        <el-select v-model="select_platform" placeholder="请选择包管理器">
          <el-option
              v-for="item in [{value:0, label:'Maven'},]"
              :key="item.value"
              :label="item.label"
              :value="item.value">
          </el-option>
        </el-select>
      </el-col>
      <el-col :span="17" class="input">
        <el-input v-model="target_pkg" :placeholder="hint_str" clearable></el-input>
      </el-col>
      <el-col :span="3" class="btn">
        <el-button class="btn-analyse" type="primary" icon="el-icon-aim" @click="analyse"> 分析</el-button>
      </el-col>
    </el-row>
    <el-row class="output-block" v-if="class_list.length!==0">
      <el-row class="package-analysis">
        <el-row class="info-block">
          <el-col :span="21" class="info-content">
            <el-row class="info-name">
              {{ pkg_info.name }}
            </el-row>
            <el-row class="info-subtitle">
              Release {{ pkg_info.version }}
            </el-row>
            <el-row class="info-descri">
              {{ pkg_info.info }}
            </el-row>
            <el-row class="info-url">
              <el-col :span="1">
                <el-image
                    style="height: 26px"
                    :src="require('../../assets/github_logo.png')"
                    fit="fit"
                >
                </el-image>
              </el-col>
              <el-col class="_url" :span="23" style="line-height: 26px; margin-left: -10px" @click.native="to_url(pkg_info.repo_url)">
                {{ pkg_info.repo_url }}
              </el-col>
            </el-row>
          </el-col>
          <el-col :span="3" class="info-opt">
            <el-row>
              <el-button class="info-button" @click="to_pkg_detail(pkg_info.ID)">查看详情</el-button>
            </el-row>
            <el-row>
              <el-button class="info-button">检查漏洞</el-button>
            </el-row>

          </el-col>
        </el-row>
      </el-row>
      <el-row class="class-analysis">
        <el-row class="_left _bold">
          类/接口信息
        </el-row>
        <el-row>
          <el-col :span="19" class="table">
            <el-row>
              <el-table
                  :data="class_list.filter(data => !table_search_input || data.className.toLowerCase().includes(table_search_input.toLowerCase()))"
                  stripe border
                  style="width: 100%;"
                  class="table-body"
                  v-loading="is_table_loading"
                  @expand-change="get_class_method"
              >
                <el-table-column
                    type="index"
                    width="50"
                    align="center">
                </el-table-column>
                <el-table-column
                    :label="'类名/接口名'+'('+class_list.length+')'"
                    prop="className"
                    :filters="[{ text: '类', value: false }, { text: '接口', value: true }]"
                    :filter-method="filter_class"
                >
                  <template slot="header" slot-scope="scope">
                    <span>
                      类名/接口名 ({{class_list.length}})
                    </span>
                    <el-input class="header-input" v-model="table_search_input" size="mini" placeholder="输入关键字搜索"/>
                  </template>
                  <template slot-scope="scope">
                    <el-row class="col_name">
                      <el-tag v-if="scope.row.isInterface" class="tag" type="success">Interface</el-tag>
                      <el-tag v-else class="tag">Class</el-tag>
                      <span style="padding-left: 10px">{{ scope.row.className }}</span>
                    </el-row>
                  </template>
                </el-table-column>
                <el-table-column
                    label="抽象类"
                    prop="isAbstract"
                    width="100"
                    align="center"
                    :filters="[{ text: '抽象类', value: true }, { text: '非抽象类', value: false }]"
                    :filter-method="filter_abstract"
                >
                  <template slot-scope="scope">
                    <el-row class="col_judge">
                      <el-tag v-if="scope.row.isAbstract" class="tag" type="success">是</el-tag>
                      <el-tag v-else class="tag" type="info">否</el-tag>
                    </el-row>
                  </template>
                </el-table-column>
                <el-table-column
                    label="最终类"
                    prop="isFinal"
                    width="100"
                    align="center"
                    :filters="[{ text: '最终类', value: true }, { text: '非最终类', value: false }]"
                    :filter-method="filter_final"
                >
                  <template slot-scope="scope">
                    <el-row class="col_judge">
                      <el-tag v-if="scope.row.isFinal" class="tag" type="warning">是</el-tag>
                      <el-tag v-else class="tag" type="info">否</el-tag>
                    </el-row>
                  </template>
                </el-table-column>
                <el-table-column
                    type="expand"
                    width="60"
                >
                  <template slot="header" slot-scope="scope">
                    <el-row>
                      详情
                    </el-row>
                  </template>
                  <template slot-scope="props">
                    <el-row class="method-block" v-loading="is_method_loading">
                      <el-row class="method-title">类/接口方法({{ class_methods_dict[props.row.id].length }})</el-row>
                      <el-col class="method-card-block" v-for="method in class_methods_dict[props.row.id]" :span="12">
                        <el-card
                            :class="{'card-green': method.isAbstract, 'card-orange': method.isFinal, 'card-gray': method.isStatic}"
                            class="method-card">
                          <el-col :span="21">
                            <el-row class="card-title">
                              {{ method_name(method.id) }}
                            </el-row>
                            <el-row class="card-tag">
                              <el-col class="_green" :span="6" v-if="method.isAbstract">
                                abstract
                              </el-col>
                              <el-col class="_gray" :span="6" v-if="method.isStatic">
                                static
                              </el-col>
                              <el-col class="_orange" :span="6" v-if="method.isFinal">
                                final
                              </el-col>
                            </el-row>
                          </el-col>
                          <el-col
                              :class="{'_green': method.isAbstract, '_orange': method.isFinal, '_gray': method.isStatic}"
                              class="opt-hidden"
                              :span="3"
                          >
                            <el-col class="_cursor" @click.native="to_call_graph(method.id)">调用图</el-col>
                          </el-col>
                        </el-card>
                      </el-col>
                    </el-row>
                  </template>
                </el-table-column>
              </el-table>
            </el-row>
          </el-col>
          <el-col :span="5" class="chart">
            <el-row id="chart-class" class="chart-1"></el-row>
            <el-row id="chart-abstract" class="chart-2"></el-row>
          </el-col>
        </el-row>
      </el-row>
    </el-row>
    <el-dialog
        class="graph-block"
        :title="graph_title"
        :visible.sync="is_graph_visible"
        width="70%"
        top="10vh"
    >
      <el-row ref="viz_call_graph" id="viz_call_graph" class="graph-body"></el-row>
    </el-dialog>
  </el-row>
</template>

<script>
import axios from "axios";
import NeoVis from 'neovis.js/dist/neovis.js';
import * as echarts from "echarts";

export default {
  name: "CallGraphAnalysis",
  data() {
    return {
      select_platform: "",
      target_platform: "",
      target_pkg: "commons-logging:commons-logging@1.0",
      pkg_info: {},
      class_list: [],
      class_methods_dict: {},
      table_search_input: "",
      // 组件配置相关
      is_table_loading: false,
      is_method_loading: false,
      is_graph_visible: false,
      graph_title: "调用图",
      // 图谱配置相关
      labels: {
        Package: {
          caption: 'id',
          size: "1",
          font: { size: 12, color: '#2c3e50' },
        },
        Class: {
          caption: 'className',
          size: "1",
          font: { size: 12, color: '#2c3e50' },
        },
        Method: {
          caption: 'Name',
          size: "1",
          font: { size: 12, color: '#2c3e50' },
        },
      },
      relationships: {
        ClassCall: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        DependOn: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        HasClass: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        HasMethod: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        MethodCall: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        MethodExtend: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
      },
    }
  },
  methods: {
    // 接口相关
    analyse: function () {
      // 预处理
      this.target_platform = this.select_platform
      let platform = this.target_platform
      if (platform === "") {
        this.$message.error('请选择分析平台')
        return
      }
      let pkg = this.target_pkg.replace(/(^\s*)|(\s*$)/g, "");
      if (pkg === "") {
        this.$message.error('请输入待分析软件包ID')
        return
      }
      // loading
      this.is_table_loading = true
      // axios
      axios({
        method: "get",
        params: { pkg: pkg, platform: platform },
        url: "http://localhost:8000/search/callgraph_analyse_pkg"
      }).then(res => {
        if (res.status !== 200) {
          this.$message.error('分析软件包出现未知错误')
          this.is_table_loading = false
        } else {
          this.class_list = res.data.data
          if (this.class_list.length === 0) {
            this.$message('软件包细粒度信息未收录');
            this.is_table_loading = false
            return
          }
          // 获取软件包信息
          axios({
            method: "get",
            params: { pkg_id: pkg, platform: Number(platform) + 1 },
            url: "http://localhost:8000/search/pkg_detail"
          }).then(res => {
            if (res.status !== 200) {
              this.$message.error('获取包详细信息时出现未知错误')
            } else {
              console.log(res.data)
              this.pkg_info = res.data.data
            }
          })
          // 统计图表信息
          this.$nextTick(function () {
            // DOM更新后
            this.init_echart()
            this.set_chart()
          })
          this.is_table_loading = false
        }
      })
    },
    get_class_method: function (row) {
      // 预处理
      let platform = this.target_platform
      let class_id = row.id
      // loading
      this.is_method_loading = true
      // axios
      axios({
        method: "get",
        params: { class: class_id, platform:platform },
        url: "http://localhost:8000/search/callgraph_analyse_class"
      }).then(res => {
        if (res.status !== 200) {
          this.$message.error('获取类详情出现未知错误')
          this.is_method_loading = false
        } else {
          // console.log(res.data.data)
          this.class_methods_dict[class_id] = res.data.data
          this.is_method_loading = false
        }
      })
    },
    // 图谱相关
    init_graph: function() {
      let config = {
        container_id: 'viz_call_graph',
        server_url: 'bolt://211.71.15.39:7687',
        server_user: 'neo4j',
        server_password: 'callgraph',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        initial_cypher: "",
      }
      this.viz_call_graph = new NeoVis(config)
      this.viz_call_graph.render()
    },
    node_click_method_call: function(node) {
      this.viz_call_graph._network.off('click')
      const statement ='match p = (n)-[r]-(:Method) where id(n)= ' + node.nodeId + ' return p limit 100'
      this.viz_call_graph.updateWithCypher(statement)
    },
    // 图表相关
    init_echart() {
        this.chart_class = echarts.init(document.getElementById("chart-class"));
        this.chart_abstract = echarts.init(document.getElementById("chart-abstract"));
    },
    set_chart: function() {
      // 统计类与接口数/抽象类与非抽象类数
      let cnt_class = 0, cnt_interface = 0, cnt_abs = 0, cnt_nabs = 0
      for (let i=0; i<this.class_list.length; i++) {
        if (this.class_list[i].isInterface) {
          cnt_interface++
        } else {
          cnt_class++
        }
        if (this.class_list[i].isAbstract) {
          cnt_abs++
        } else {
          cnt_nabs++
        }
      }
      this.update_echarts_class(cnt_class, cnt_interface)
      this.update_echarts_abstract(cnt_nabs, cnt_abs)
    },
    update_echarts_class(arg1, arg2) {
      const option = {
        legend: {
          // 图例
          data: ["类", "接口"],
          top: "90%",
          orient: "horizontal"
        },
        title: {
          text: "类/接口",
          textStyle: {fontSize: 15, color:"#2c3e50"},
          y: "41%",
          x: "center"
        },
        series: [
          {
            top: "0%",
            type: "pie",
            center: ['50%', '46%'],
            label: {
              show: true,
              formatter: "{c}" // b代表名称，c代表对应值，d代表百分比
            },
            radius: ["40%", "70%"], //圆环内半径40%，外半径70%
            data: [
              {
                value: arg1,
                name: "类",
              },
              {
                value: arg2,
                name: "接口"
              }
            ]
          }
        ]
      };
      this.chart_class.setOption(option);
    },
    update_echarts_abstract(arg1, arg2) {
      const option = {
        legend: {
          // 图例
          data: ["非抽象类", "抽象类"],
          top: "90%",
          orient: "horizontal"
        },
        title: {
          text: "类构成",
          textStyle: {fontSize: 15, color:"#2c3e50"},
          y: "41%",
          x: "center"
        },
        series: [
          {
            top: "0%",
            type: "pie",
            center: ['50%', '46%'],
            label: {
              show: true,
              formatter: "{c}" // b代表名称，c代表对应值，d代表百分比
            },
            radius: ["40%", "70%"], //圆环内半径40%，外半径70%
            data: [
              {
                value: arg1,
                name: "非抽象类"
              },
              {
                value: arg2,
                name: "抽象类"
              }
            ]
          }
        ]
      };
      this.chart_abstract.setOption(option);
    },
    // 组件配置相关
    filter_abstract(value, row) {
      return row.isAbstract === value;
    },
    filter_class(value, row) {
      return row.isInterface === value;
    },
    filter_final(value, row) {
      return row.isFinal === value
    },
    // 其他
    method_name: function (id) {
      let temp_split = id.split(':')
      return temp_split[temp_split.length-1]
    },
    to_call_graph: function (method_id) {
      this.is_graph_visible = true
        this.$nextTick(function () {
          // DOM更新后
          this.init_graph()
          const statement = 'match p = (n:Method{id:"' + method_id + '"})-[r*0..1]-(:Method) return p'
          console.log(statement)
          this.viz_call_graph.updateWithCypher(statement)
          // 注册点击事件
          this.viz_call_graph.registerOnEvent('clickNode', this.node_click_method_call)
        })
    },
    to_pkg_detail: function (pkg_id) {
      this.$emit("change_tag", { to: 2, pkg: pkg_id, platform: this.target_platform==="0"?"maven":"" })
    }
  },
  computed: {
    hint_str() {
      if (this.target_platform === 0) {
        return "请输入Maven包坐标 ( [groupId]:[artifactId]@[version] )"
      }
      else {
        return ""
      }
    },
  },
  mounted() {

  },
}
</script>

<style scoped>
.call-graph-analysis {
  padding: 20px;
}

.input-block {
  transition: .3s;
}
.input-block .select {
  padding-right: 20px;
}
.input-block .btn {
  text-align: right;
}
.input-block .btn-analyse {
  width: 80%;
}

.input-before {
  width: 90%;
  margin-left: 10%;
  margin-top: 18%;
}
.input-after {
  width: 100%;
}

.output-block {
  margin-top: 20px;
  text-align: left;
}

.package-analysis .info-block {
  margin-top: 5px;
  padding: 20px 30px;
  border: gainsboro 1px solid;
  border-radius: 10px;
}
.package-analysis .info-content {
  border-right: gainsboro 1px solid;
}
.package-analysis .info-name {
  font-size: 36px;
}
.package-analysis .info-subtitle {
  margin-top: 12px;
  font-size: 24px;
  color: grey;
}
.package-analysis .info-descri {
  margin-top: 12px;
  line-height: 24px;
  padding-right: 12px;
  text-align: justify;
}
.package-analysis .info-url {
  margin-top: 10px;
}
.package-analysis .info-opt {
  padding: 20px;
  text-align: center;
}
.package-analysis .info-button {
  margin-top: 12px;
  padding: 10px 18px;
}

.class-analysis {
  margin-top: 20px;
}
.class-analysis .table {
  margin-top: 20px;
}
.class-analysis .header-input {
  width: 50%;
  margin: 0 10px;
}
.class-analysis .col_name .tag {
  text-align: center;
  padding: 0 5px;
  line-height: 26px;
  font-size: 12px;
  height: 28px;
  width: 60px;
}
.class-analysis .col_judge .tag {
  text-align: center;
  padding: 0 5px;
  line-height: 26px;
  font-size: 12px;
  height: 28px;
  width: 28px;
}
.class-analysis .el-tag.el-tag--success {
  background-color: #f0f9eb;
  border-color: #e1f3d8;
  color: #52ad26;
}
.class-analysis .chart-1 {
  margin-top: 20px;
  margin-left: 20px;
  height: 180px;
  width: 270px;
}
.class-analysis .chart-2 {
  margin-top: 20px;
  margin-left: 20px;
  height: 180px;
  width: 270px;
}


.method-block {
  padding: 4px 20px;
}
.method-block .method-title {
  font-weight: bold;
  font-size: 15px;
  margin-bottom: 5px;
}
.method-block .method-card-block {
  padding-right: 15px;
  padding-bottom: 10px;
  padding-top: 10px;
}
.method-block .method-card {
  height: 74px;
  overflow-y: auto;
  border-left: #0e90ff 5px solid;
  background-color: #ecf5ff;
}
.method-block .card-green {
  border-left: #67C23A 5px solid;
  background-color: #f0f9eb;
}
.method-block .card-orange {
  border-left: #E6A23C 5px solid;
  background-color: #fdf6ec;
}
.method-block .card-blue {
  border-left: #0e90ff 5px solid;
  background-color: #ecf5ff;
}
.method-block .card-gray {
  border-left: #909399 5px solid;
  background-color: #f4f4f5;
}

.method-card-block .card-title {
  margin-bottom: 10px;
  word-wrap: break-word;
  line-height: 18px;
}
.method-card-block >>> .el-card__body {
  padding: 14px 20px;
}
.method-card-block .opt-hidden {
  color: #0e90ff;
  font-size: 15px;
  line-height: 46px;
  opacity: 0;
  transition: .2s ease;
}
.method-card:hover .opt-hidden {
  opacity: 1;
}

.graph-block >>> .el-dialog__body {
  padding: 20px;
}
.graph-block .graph-body {
  width: 100%;
  height: 500px;
}

.el-button--primary {
  background-color: #0e90ff;
  font-size: 15px;
}
.el-button--primary:hover {
  background-color: #1ea4ff;
}

.class-analysis .el-table >>> thead {
  color: #606266;
  font-size: 15px;
}

.class-analysis >>> .el-table__body-wrapper {
  max-height: 405px;
  overflow-y: auto;
}
.class-analysis >>> .el-table__body-wrapper::-webkit-scrollbar{
  width: 6px;
}
.class-analysis >>> .el-table__body-wrapper::-webkit-scrollbar-thumb{
  border-radius: 3px;
  min-height: 30px;
  background: rgba(144,147,153,.3);
}

.class-analysis .table-body >>> i {
  font-size: 16px;
}

._left {
  text-align: left;
}
._bold {
  font-weight: bold;
}
._cursor:hover {
  cursor: pointer;
}

._green {
  color: #67C23A !important;
}
._blue {
  color: #0e90ff !important;
}
._orange {
  color: #E6A23C !important;
}
._pink {
  color: #ff5985 !important;
}
._gray {
  color: #909399 !important;
}
._light_gray {
  color: #ebeef5 !important;
}

._url {
  color: #5192ff;
}
._url:hover {
  color: #2373ff;
  cursor: pointer;
}
</style>